﻿#region Imports
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Forms;
using CommonUtilities.Attributes;
using CommonUtilities.Enums;
using CommonUtilities.Models;
using CommonUtilities.Properties;
using CommonUtilities.Util;
using CommonUtilities.Views;
using Microsoft.VisualBasic;
#endregion Imports

namespace CommonUtilities.Extensions
{
    /// <summary>
    /// Extension methods for misc. func.
    /// </summary>
    /// <seealso cref="http://stackoverflow.com/questions/249222/can-i-add-extension-methods-to-an-existing-static-class" />
    /// <seealso cref="http://stackoverflow.com/questions/866921/static-extension-methods" />
    /// <seealso cref="http://msdn.microsoft.com/en-us/library/vstudio/bb383977.aspx" />
    public static class MiscExtensions
    {
        #region Declarations
        /// <summary>
        /// 5
        /// </summary>
        const double SHIFT_DEFAULT = 5;

        /// <summary>
        /// "N/A"
        /// </summary>
        const string DEFAULT_SIZE = "N/A";

        /// <summary>
        /// "{0}/{1}"
        /// </summary>
        const string SUBDIR_FORMAT = "{0}/{1}";
        #endregion Declarations

        #region Extension methods
        #region Path/Folder/File
        public static string GetFolderPath(this Environment.SpecialFolder value)
        {
            return Environment.GetFolderPath(value);
        }

        public static long GetLength(this FileInfo fInfo)
        {
            try
            {
                return fInfo.Length;
            }
            catch (Exception)
            {
                return (long)0;
            }
        }

        /// <summary>
        /// Validates the format of a path with Regular Expression
        /// </summary>
        /// <param name="path">Path to evaluate</param>
        /// <returns>TRUE: if path is valid</returns>
        public static bool IsPathFormatValid(this string path)
        {
            string regxprsn;
            regxprsn = @"^(?:[a-zA-Z]\:|\\\\[\w\.]+\\[\w.]+)\\(?:[\w]+\\)*\w([\w.])+$"; // RKD
            //regxprsn = @"^(?:[a-zA-Z]\:(\\|\/)|file\:\/\/|\\\\|\.(\/|\\))([^\\\/\:\*\?\<\>\"\|]+(\\|\/){0,1})+$"

            return !path.ToCharArray().Where(ch => Path.GetInvalidPathChars().Contains(ch)).Any()
                && !path.IsNullOrEmpty() && path.Contains(Path.VolumeSeparatorChar)
                && (path.Split(Path.VolumeSeparatorChar).Any() ? path.Split(Path.VolumeSeparatorChar).First().Length == 1 : false); // Regex.IsMatch(p, regxprsn);
        }

        /// <summary>
        /// Evaluates if the specified path is valid
        /// </summary>
        /// <param name="path">Path to evaluate</param>
        /// <param name="validationType">Validation type</param>
        /// <returns>TRUE: if path is valid</returns>
        public static bool ValidatePath(this string path, ePathValidation validationType)
        {
            bool result = !path.IsNullOrEmpty();

            if (result)
            {
                switch (validationType)
                {
                    case ePathValidation.Directory:
                        result = result && Directory.Exists(path) && File.GetAttributes(path).HasFlag(FileAttributes.Directory);
                        break;
                    case ePathValidation.File:
                        result = result && File.Exists(path) && !File.GetAttributes(path).HasFlag(FileAttributes.Directory);
                        break;
                    default:
                        break;
                }
            }

            return result;
        }

        /// <summary>
        /// Fetches directory name from specified path (not full path)
        /// <para>Note: Use &apos;<code>GetDirectoryNameFull(string path)</code>&apos; instead, to get full path</para>
        /// </summary>
        /// <param name="path">Path to evluate</param>
        /// <returns>Directory name for the specified path</returns>
        public static string GetDirectoryNameOnly(this string path)
        {
            return new DirectoryInfo(path).Name;
        }

        /// <summary>
        /// Fetches directory name from specified path (full path)
        /// </summary>
        /// <param name="path">Path to evluate</param>
        /// <returns>Directory name for the specified path</returns>
        public static string GetDirectoryNameFull(this string path)
        {
            return Path.GetDirectoryName(path); // new DirectoryInfo(path).FullName;
        }

        /// <summary>
        /// Gets directory by specified direction
        /// </summary>
        /// <param name="path">Current path</param>
        /// <param name="direction">Direction for navigation</param>
        /// <param name="isValid">TRUE: Directory can be navigated towards specified direction</param>
        /// <param name="sLocation">Location index (#/#)</param>
        /// <param name="allDirectories">List of directories</param>
        /// <returns></returns>
        public static string GetDirectoryByDirection(this string path, eFetchTypes direction,
            out bool isValid, out string sLocation, ObservableCollection<string> allDirectories = null)
        {
            sLocation = Resources.Const_NotAvailable;
            switch (direction)
            {
                case eFetchTypes.Prev: return path.GetDirectoryPrev(out isValid, out sLocation, allDirectories);
                case eFetchTypes.Next: return path.GetDirectoryNext(out isValid, out sLocation, allDirectories);
                case eFetchTypes.Up: return path.GetDirectoryUp(out isValid, out sLocation);
                case eFetchTypes.Down: return path.GetDirectoryDown(out isValid, out sLocation);

                case eFetchTypes.All:
                default:
                    throw new InvalidOperationException("Specified value for {0} is not allowed".FormatTextFromParams(direction.GetType()));
            }
        }

        /// <summary>
        /// Gets directory one level up
        /// </summary>
        /// <param name="path">Current path</param>
        /// <param name="isValid">TRUE: Directory can be navigated towards specified direction</param>
        /// <param name="sLocation">Location index (#/#)</param>
        /// <returns></returns>
        public static string GetDirectoryUp(this string path, out bool isValid, out string sLocation)
        {
            string result;

            isValid = !Directory.GetParent(path).IsNull() && path.ToLower() != Directory.GetDirectoryRoot(path).ToLower();
            result = isValid ? Directory.GetParent(path).FullName : "";

            sLocation = GenerateSubDirectoryCount(isValid, result, Utilities.GetValidDirectories(result).GetCount());
            return result;
        }

        /// <summary>
        /// Gets directory by navigating specified levels up
        /// </summary>
        /// <param name="path">Current path</param>
        /// <param name="nLevels">No. of levels to navigate upwards</param>
        /// <param name="throwException">TRUE: Throws exception</param>
        /// <returns></returns>
        public static string GetDirectoryUp(this string path, int nLevels = 1, bool throwException = false)
        {
            while (nLevels > 0)
            {
                try
                {
                    path = Directory.GetParent(path).FullName;
                    nLevels--;
                }
                catch (Exception) { if (throwException) throw; else return path; }
            }

            return path;
        }

        /// <summary>
        /// Gets directory one level down
        /// </summary>
        /// <param name="path">Current path</param>
        /// <param name="isValid">TRUE: Directory can be navigated towards specified direction</param>
        /// <param name="sLocation">Location index (#/#)</param>
        /// <returns></returns>
        public static string GetDirectoryDown(this string path, out bool isValid, out string sLocation)
        {
            string result;
            var validDirectories = Utilities.GetValidDirectories(path);

            isValid = validDirectories.Any();
            result = isValid ? validDirectories.FirstOrDefault() : "";

            sLocation = GenerateSubDirectoryCount(isValid, result, validDirectories.GetCount());
            return result;
        }

        /// <summary>
        /// Gets previous directory at same level
        /// </summary>
        /// <param name="path">Current path</param>
        /// <param name="isValid">TRUE: Directory can be navigated towards specified direction</param>
        /// <param name="sLocation">Location index (#/#)</param>
        /// <param name="allDirectories">List of directories</param>
        /// <returns></returns>
        public static string GetDirectoryPrev(this string path, out bool isValid, out string sLocation, ObservableCollection<string> allDirectories = null)
        {
            if (!allDirectories.IsLinqIterable(false))
                allDirectories = Utilities.GetValidDirectories(Directory.GetParent(path).FullName).ToObservableCollection();

            int currDirectoryIndex = allDirectories.IndexOf(path);
            isValid = (currDirectoryIndex > 0);
            sLocation = GenerateSubDirectoryCount(isValid, currDirectoryIndex, allDirectories.GetCount());

            return isValid ? allDirectories.ElementAt(currDirectoryIndex - 1) : "";
        }

        /// <summary>
        /// Gets next directory at same level
        /// </summary>
        /// <param name="path">Current path</param>
        /// <param name="isValid">TRUE: Directory can be navigated towards specified direction</param>
        /// <param name="sLocation">Location index (#/#)</param>
        /// <param name="allDirectories">List of directories</param>
        /// <returns></returns>
        public static string GetDirectoryNext(this string path, out bool isValid, out string sLocation, ObservableCollection<string> allDirectories = null)
        {
            if (!allDirectories.IsLinqIterable(false))
                allDirectories = Utilities.GetValidDirectories(Directory.GetParent(path).FullName).ToObservableCollection();

            int currDirectoryIndex = allDirectories.IndexOf(path);
            isValid = (currDirectoryIndex + 1 < allDirectories.Count());
            sLocation = GenerateSubDirectoryCount(isValid, currDirectoryIndex, allDirectories.GetCount());

            return isValid ? allDirectories.ElementAt(currDirectoryIndex + 1) : "";
        }

        /// <summary>
        /// Generates sub-directory count (for Up/Down only)
        /// </summary>
        /// <param name="isValid">TRUE: Directory can be navigated towards specified direction</param>
        /// <param name="result">Evaluated directory</param>
        /// <param name="childCount">Count of sub-directories</param>
        /// <returns></returns>
        private static string GenerateSubDirectoryCount(bool isValid, string result, long childCount)
        {
            return (isValid & childCount > 0) ? SUBDIR_FORMAT.FormatTextFromParams(1, childCount) : Resources.Const_NotAvailable;
        }

        /// <summary>
        /// Generates sub-directory count (for Prev/Next only)
        /// </summary>
        /// <param name="isValid">TRUE: Directory can be navigated towards specified direction</param>
        /// <param name="currDirectoryIndex">Current directory index</param>
        /// <param name="childCount">Count of sub-directories</param>
        /// <returns></returns>
        private static string GenerateSubDirectoryCount(bool isValid, int currDirectoryIndex, long childCount)
        {
            return isValid || currDirectoryIndex == 0 || currDirectoryIndex == childCount - 1
                ? SUBDIR_FORMAT.FormatTextFromParams(currDirectoryIndex + 1, childCount) : Resources.Const_NotAvailable;
        }

        public static string AppendPath(this string value, params string[] paths)
        {
            value = value.Coalesce().RemoveText(Path.GetInvalidPathChars()); // Clean source path

            if (paths.Length == 0) return value; // Return source string if paths are not specified
            else paths = paths.Select(p => p.Coalesce().RemoveText(Path.GetInvalidPathChars()).TrimEnd("\\".ToCharArray())).ToArray(); // Clean paths

            if (!value.IsNullOrEmpty())
            {
                if (!value._Contains(Path.VolumeSeparatorChar) && value.Length == 1) // If base is empty, append VolumeSeparatorChar
                    value += Path.VolumeSeparatorChar;
                if (value._Contains(Path.VolumeSeparatorChar)) // If base has VolumeSeparatorChar, trim VolumeSeparatorChar from all paths
                    paths = paths.Select(p => p.TrimEnd(Path.VolumeSeparatorChar)).ToArray();
            }
            else if (value.IsNullOrEmpty()) // If base is empty, allow VolumeSeparatorChar only for first path
            {
                paths = paths.Select(p => p.TrimEnd(Path.VolumeSeparatorChar)).Reverse().ToArray();
                if (paths[0].Length == 1) paths[0] += Path.VolumeSeparatorChar;
            }

            return (value.IsBlank() ? "" : value.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar) + Path.Combine(paths);
        }

        /// <summary>
        /// Formats extension case of a file
        /// </summary>
        /// <param name="path">Path to format file extension for</param>
        /// <param name="extnCase">Case of the file extension</param>
        /// <returns>Path with formatted file extension</returns>
        public static string FormatPathExtnCase(this string path, eExtnCase extnCase)
        {
            string extn = "";
            char cExtnSeparator = '.';

            if (path.ValidatePath(ePathValidation.File))
            {
                switch (extnCase)
                {
                    case eExtnCase.LCase:
                        extn = Path.GetExtension(path).ToLower();
                        break;

                    case eExtnCase.UCase:
                        extn = Path.GetExtension(path).ToUpper();
                        break;

                    case eExtnCase.SameCase:
                    default:
                        extn = Path.GetExtension(path);
                        break;
                }

                return (Path.GetFileNameWithoutExtension(path) + cExtnSeparator + extn).TrimEnd(cExtnSeparator);
            }
            else return path;
        }

        public static string GetFormattedPath(this string path)
        {
            if (path.IsAlpha(1)) path = path.ToUpper() + Path.VolumeSeparatorChar;
            return path.Trim(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
        }
        #endregion Path/Folder/File

        #region String
        /// <summary>
        /// Indicates whether the specified string is null or an System.String.Empty string.
        /// </summary>
        /// <param name="value">String to evaluate</param>
        /// <returns>true if the value parameter is null or an empty string (""); otherwise, false</returns>
        /// <exception cref="T:System.ArgumentNullException">Value is null</exception>
        public static bool IsNullOrEmpty(this string value)
        {
            return String.IsNullOrEmpty(value);
        }

        /// <summary>
        /// Indicates whether the specified string is null or an System.String.Empty string.
        /// </summary>
        /// <param name="value">String to evaluate</param>
        /// <param name="defaultValue">Default value to return when evaluated to FALSE</param>
        /// <returns>TRUE if the value parameter is null or an empty string (""); otherwise, FALSE</returns>
        /// <exception cref="T:System.ArgumentNullException">Value is null</exception>
        public static string IsNullOrEmpty(this string value, string defaultValue)
        {
            return value.IsNullOrEmpty() ? defaultValue : value;
        }

        /// <summary>
        /// Indicates whether a specified string is null, empty, or consists only of white-space characters.
        /// </summary>
        /// <param name="value">The string to test.</param>
        /// <returns>
        /// TRUE if the value parameter is null or <code>System.String.Empty,</code>
        /// or if value consists exclusively of white-space characters.
        /// </returns>
        public static bool IsNullOrWhiteSpace(this string value)
        {
            return String.IsNullOrWhiteSpace(value);
        }

        /// <summary>
        /// Indicates whether a specified string is null, empty, or consists only of white-space characters.
        /// </summary>
        /// <param name="value">The string to test.</param>
        /// <param name="defaultValue">Default value to return when evaluated to FALSE</param>
        /// <returns>
        /// TRUE if the value parameter is null or <code>System.String.Empty,</code>
        /// or if value consists exclusively of white-space characters.
        /// </returns>
        public static string IsNullOrWhiteSpace(this string value, string defaultValue)
        {
            return value.IsNullOrWhiteSpace() ? defaultValue : value;
        }

        /// <summary>
        /// Specifies if a text field contains value or blank/whitespace/null
        /// </summary>
        /// <param name="value">Text to evaluate</param>
        /// <returns></returns>
        public static bool IsBlank(this string value)
        {
            return value.IsNullOrEmpty() || value.IsNullOrWhiteSpace();
        }

        /// <summary>
        /// Returns a string containing a specified number of characters from the left/right side of a string
        /// </summary>
        /// <param name="value">Text to evaluate</param>
        /// <param name="length">No. of characters to return from left/right
        /// <para>length = 0 returns a zero-length string (quot;quot;)</para>
        /// <para>length greater than or equal to the no. of characters returns the entire string</para></param>
        /// <param name="trimDirection">Trim from direction</param>
        /// <param name="throwException">TRUE: throws exception when provided trim length is greater than strin length
        /// <para>FALSE: skips exception (useful when max length is specified for dynamic text)</para></param>
        /// <returns></returns>
        public static string TrimAndTake(this string value, int length, eTrimDirection trimDirection, bool throwException = true)
        {
            if (value.IsNullOrEmpty()) value = "";
            if (length < 0) throw new ArgumentException("Length cannot be negative.");
            else if (length > value.Length)
            {
                if (throwException) throw new ArgumentException("Specified trim length is greater than string.");
                else length = value.Length;
            }

            switch (trimDirection)
            {
                case eTrimDirection.Right:
                    return Strings.Right(value, length);
                case eTrimDirection.Left:
                default:
                    return Strings.Left(value, length);
            }
        }

        /// <summary>
        /// Returns a string leaving out a specified number of characters from the left/right side of a string
        /// <para>(equivalent to TrimStart/TrimEnd)</para>
        /// </summary>
        /// <param name="value">Text to evaluate</param>
        /// <param name="length">No. of characters to leave out from left/right</param>
        /// <param name="trimDirection">Trim from direction</param>
        /// <param name="throwException">TRUE: throws exception when provided trim length is greater than strin length
        /// <para>FALSE: skips exception (useful when max length is specified for dynamic text)</para></param>
        /// <returns></returns>
        public static string TrimAndLeave(this string value, int length, eTrimDirection trimDirection, bool throwException = true)
        {
            if (value.IsNullOrEmpty()) value = "";
            if (length < 0) throw new ArgumentException("Length cannot be negative.");
            else if (length > value.Length)
            {
                if (throwException) throw new ArgumentException("Specified trim length is greater than string.");
                else length = value.Length;
            }

            switch (trimDirection)
            {
                case eTrimDirection.Left:
                    trimDirection = eTrimDirection.Right;
                    break;
                case eTrimDirection.Right:
                    trimDirection = eTrimDirection.Left;
                    break;
                default:
                    break;
            }

            return value.TrimAndTake(value.Length - length, trimDirection);
        }

        /// <summary>
        /// Removes text from specified string
        /// </summary>
        /// <param name="value">Text to be operated on</param>
        /// <param name="remove">Array of text to be removed</param>
        /// <returns>Text after removing specified part</returns>
        public static string RemoveText(this string value, params string[] remove)
        {
            foreach (string txt in remove) value = value.Replace(txt, "");
            return value;
        }

        /// <summary>
        /// Removes text from specified string
        /// </summary>
        /// <param name="value">Text to be operated on</param>
        /// <param name="remove">Array of characters to be removed</param>
        /// <returns>Text after removing specified part</returns>
        public static string RemoveText(this string value, params char[] remove)
        {
            return value.RemoveText(remove.ConvertAll(chr => Convert.ToString(chr)).ToArray());
        }

        /// <summary>
        /// Appends collection of text
        /// </summary>
        /// <param name="values">Collection of texts to append</param>
        /// <param name="delimeter">Delimeter between text</param>
        /// <returns></returns>
        public static string AppendText<TSource>(this IEnumerable<TSource> values, string delimeter = " ")
        {
            return String.Concat(values._Select(val => val.ToString()).RemoveEmptyWords()
                ._Select(str => str += delimeter)).TrimEnd(delimeter.ToCharArray());
        }

        public static string AppendText<TSource>(this IEnumerable<TSource> values, char delimeter = ' ')
        {
            return values.AppendText(delimeter.ToString());
        }

        public static string AppendText(this string value, string delimeter = " ", params string[] text)
        {
            return value + delimeter + text.AppendText(delimeter);
        }

        public static string AppendText(this string value, char delimeter = ' ', params string[] text)
        {
            return value.AppendText(delimeter.ToString(), text);
        }

        public static string AppendText<TSource, TResult>(this IEnumerable<TSource> values,
            Func<TSource, TResult> selector, string delimeter = " ", Func<TSource, bool> predicate = null)
        {
            if (predicate != null) return values.Where(predicate).Select(selector).AppendText(delimeter);
            else return values.Select(selector).AppendText(delimeter);
        }

        public static string AppendText<TSource, TResult>(this IEnumerable<TSource> values,
            Func<TSource, TResult> selector, char delimeter = ' ', Func<TSource, bool> predicate = null)
        {
            return values.AppendText(selector, delimeter.ToString(), predicate);
        }

        #region Words
        /// <summary>
        /// Converts the specified text to a list of words (with punctuations)
        /// </summary>
        /// <param name="value">Text to be converted from</param>
        /// <returns>List of words (with punctuations)</returns>
        public static IEnumerable<string> ToWords(this string value)
        {
            return value.Coalesce().Split(' ');
        }

        /// <summary>
        /// Converts the specified list of words to sentence
        /// </summary>
        /// <param name="value">List of words to be converted from</param>
        /// <returns>Sentence containing the words</returns>
        public static string ToSentence(this IEnumerable<string> value)
        {
            return value.AppendText(" ");
        }

        /// <summary>
        /// Fetches the word from the specified sentence at the specified index
        /// </summary>
        /// <param name="value">Text to be searched into</param>
        /// <param name="index">Index of the word to be searched</param>
        /// <returns>Word at the specified index in the specified text</returns>
        public static string WordAt(this string value, int index)
        {
            IEnumerable<string> res = value.ToWords();
            return (index < res.GetCount()) ? res.ElementAt(index) : res.LastOrDefault();
        }

        /// <summary>
        /// Capitalizes each word in the specified text
        /// </summary>
        /// <param name="value">Text to be processed</param>
        /// <returns>Text with each word capitalized</returns>
        public static string CapitalizeEachWord(this string value)
        {
            string result = "";
            value.ToWords().ForEach(str => result += str.ToUpper().TrimAndTake(1, eTrimDirection.Left, false) + str.ToLower().TrimAndLeave(1, eTrimDirection.Left, false) + " ");

            return result;
        }

        /// <summary>
        /// Generates initials for specified text
        /// </summary>
        /// <param name="value">Text to generate initials for</param>
        /// <param name="delimeter">Delimeter for the initials</param>
        /// <returns></returns>
        public static string ToInitials(this string value, string delimeter)
        {
            return AppendText(value.CapitalizeEachWord().ToWords()._Select(word => word.First().ToString()), delimeter);
        }

        /// <summary>
        /// Generates camel case for specified text
        /// </summary>
        /// <param name="value">Text to generate camel case for</param>
        /// <returns></returns>
        public static string ToCamelCase(this string value)
        {
            string result = value.ToPascalCase();
            result = (result.ToCharStringArray().FirstOrDefault().ToLower() + result.TrimAndLeave(1, eTrimDirection.Left, false));

            return result.Replace(" ", "");
        }

        /// <summary>
        /// Generates Pascal case for specified text
        /// </summary>
        /// <param name="value">Text to generate Pascal case for</param>
        /// <returns></returns>
        public static string ToPascalCase(this string value)
        {
            return value.CapitalizeEachWord().Replace(" ", "");
        }

        /// <summary>
        /// Removes empty words from specified text
        /// </summary>
        /// <param name="value">Text to remove empty words from</param>
        /// <returns></returns>
        public static string RemoveEmptyWords(this string value)
        {
            return value.ToWords().RemoveEmptyWords().ToSentence();
        }

        /// <summary>
        /// Removes empty words from specified text
        /// </summary>
        /// <param name="value">Text to remove empty words from</param>
        /// <returns></returns>
        public static IEnumerable<string> RemoveEmptyWords(this IEnumerable<string> value)
        {
            return value._Where(str => !str.IsBlank());
        }

        public static string FormatPrepAndArticles(this string value)
        {
            throw new InvalidOperationException("RKD : Dont use");

            // RKD
            string sclip = System.Windows.Forms.Clipboard.GetText();
            ObservableCollection<string> words = sclip.RemoveEmptyLines().Replace("\n", " ").Replace("\r", " ").ToWords()._Select(w => w.TrimEnd(".,;'!@#$^&()-_=+[]{}".ToCharArray())).Distinct().ToObservableCollection();
            ObservableCollection<string> preps = words._Where(w => w.Length < 5 && !w.IsNumeric())._OrderBy(w => w.Length)._ThenBy().ToObservableCollection();
            System.Windows.Forms.Clipboard.SetText("".AppendText(Utilities.cNewLine, preps.ToArray()));

            ObservableCollection<string> wordList = new ObservableCollection<string>();
            ObservableCollection<string> possibleArticlesPrepositions = wordList.RemoveItem(wordList.First())._ForEach(wrd =>
            {
                if ("A,An,The".ToLower().Split(',')._Contains(wrd.ToLower()) || wrd.Length <= 3)
                    wrd.ToLower();
            }).ToObservableCollection();

            return wordList.First().AppendText(" ", possibleArticlesPrepositions.ToArray());
        }
        #endregion Words

        public static IEnumerable<string> ToCharStringArray(this string value)
        {
            return value.ToCharArray().ToCharStringArray();
        }

        public static string TrimEmptyChars(this string value)
        {
            return Utilities.ConvertByteArrayToString(value.ConvertToByteArray(value.Length), true);
        }

        public static byte[] ConvertToByteArray(this string value, int length)
        {
            return Utilities.ConvertStringToByteArray(value, length);
        }

        public static string TrimToSize(this string value, int size = 20, bool addContinuerChar = true)
        {
            return value.Take(size)._ToString() + (value.Length > size && addContinuerChar ? " .." : "");
        }

        /// <summary>
        /// Converts a character array to string
        /// </summary>
        /// <param name="value">Character array</param>
        /// <returns>String containing merged characters</returns>
        public static string _ToString(this IEnumerable<char> value)
        {
            string str = "";
            value.ToList().ForEach(chr => str += chr);

            return str;
        }

        /// <summary>
        /// Allows console like formatting from parameters
        /// <para>(Hint: start from {0})</para>
        /// </summary>
        /// <param name="value">String to format</param>
        /// <param name="param">Params to format with</param>
        /// <returns></returns>
        public static string FormatTextFromParams(this string value, params object[] param)
        {
            // RKD : DELETE
            //int i = -1;
            //foreach (object val in param)
            //{ i++; value = value.Replace("{" + i + "}", Convert.ToString(val)); }
            //
            //return value;

            //if (!value.Contains("{0}"))
            //{
            //    StackTrace stkTrace = new StackTrace(1);
            //    StackFrame[] stkFrames = stkTrace.GetFrames();
            //    string debugInfo = "Debug info: " + Utilities.cNewLine + Utilities.GenerateDebugInfo(stkFrames.FirstOrDefault()) + Utilities.cNewLine.Repeator(2);
            //
            //    System.Windows.Forms.MessageBox.Show(debugInfo + "Parameters should start with {0}. Correct code..", "InvalidOperationException", MessageBoxButtons.OK, MessageBoxIcon.Stop);
            //}

            return String.Format(value, param);
        }

        public static string Repeator(this string value, short count)
        {
            string result = "";

            if (value.IsNull()) value = "";
            if (count < 0 | count > short.MaxValue) count = 1;
            for (int i = 1; i <= count; i++) result += value;

            return result;
        }

        public static IEnumerable<string> ConvertStringToLines(this string value)
        {
            TextBox txtTemp = new TextBox { Text = value };
            return txtTemp.Lines;
        }

        public static string ConvertLinesToString(this IEnumerable<string> value)
        {
            TextBox txtTemp = new TextBox { Lines = value.ToArray() };
            return txtTemp.Text;
        }

        public static string RemoveEmptyLines(this string value)
        {
            return value.ConvertStringToLines().RemoveEmptyLines().ConvertLinesToString();
        }

        public static IEnumerable<string> RemoveEmptyLines(this IEnumerable<string> value)
        {
            return value._Where(ln => !ln.RemoveText("\t".ToCharArray()).IsBlank());
        }

        public static string FormatStringArticles(this string source)
        {
            foreach (string article in "a|an|the".Split('|'))
            {
                string formatPrefix = article.CapitalizeEachWord() + " ";
                if (source.StartsWith(formatPrefix))
                {
                    string formatSuffix = ", " + article.ToLower();
                    source = source.RemoveText(formatPrefix) + formatSuffix;
                }
            }

            return source;
        }

        public static string RestoreStringArticles(this string source)
        {
            foreach (string article in "a|an|the".Split('|'))
            {
                string formatSuffix = ", " + article.ToLower();
                if (source.EndsWith(formatSuffix))
                {
                    string formatPrefix = article.CapitalizeEachWord() + " ";
                    source = formatPrefix + source.RemoveText(formatSuffix);
                }
            }

            return source;
        }

        /// <summary>
        /// Returns a new string that right-aligns the characters in this instance by padding them on the left with a specified Unicode character, for a specified total length.
        /// </summary>
        /// <param name="source">The value to be padded</param>
        /// <param name="totalWidth">The number of characters in the resulting string, equal to the number of original characters plus any additional padding characters.</param>
        /// <param name="paddingChar">A Unicode padding character.</param>
        /// <returns>
        /// A new string that is equivalent to this instance, but right-aligned and padded on the left with as many <paramref name="paddingChar"/> characters as needed to create a length of <paramref name="totalWidth"/>. 
        /// Or, if <paramref name="totalWidth"/> is less than the length of this instance, a new string that is identical to this instance.
        /// </returns>
        /// <exception cref="System.ArgumentOutOfRangeException">totalWidth is less than zero.</exception>
        public static string PadLeft<TStruct>(this TStruct source, int totalWidth, char paddingChar = ' ')
            where TStruct : struct
        {
            return source.ToString().PadLeft(totalWidth, paddingChar);
        }

        /// <summary>
        /// Returns a new string that left-aligns the characters in this string by padding them on the right with a specified Unicode character, for a specified total length.
        /// </summary>
        /// <param name="source">The value to be padded</param>
        /// <param name="totalWidth">The number of characters in the resulting string, equal to the number of original characters plus any additional padding characters.</param>
        /// <param name="paddingChar">A Unicode padding character.</param>
        /// <returns>
        /// A new string that is equivalent to this instance, but left-aligned and padded on the right with as many <paramref name="paddingChar"/> characters as needed to create a length of <paramref name="totalWidth"/>. 
        /// Or, if <paramref name="totalWidth"/> is less than the length of this instance, a new string that is identical to this instance.
        /// </returns>
        /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="totalWidth"/> is less than zero.</exception>
        public static string PadRight<TStruct>(this TStruct source, int totalWidth, char paddingChar = ' ')
            where TStruct : struct
        {
            return source.ToString().PadRight(totalWidth, paddingChar);
        }

        /// <summary>
        /// Removes/Restores XML escape characters
        /// </summary>
        /// <param name="data">Text to be cleared of/added with XML invalid characters</param>
        /// <param name="isRestoreOp">TRUE: Restores XML invalid characters</param>
        /// <returns></returns>
        public static string FormatXmlTags(this string data, bool isRestoreOp = false)
        {
            if (!isRestoreOp) return SecurityElement.Escape(data);
            else
            {
                Dictionary<string, string> dictXmlTags = new Dictionary<string, string>();
                dictXmlTags.AddToDictionary("&", "&amp;");
                dictXmlTags.AddToDictionary("<", "&lt;");
                dictXmlTags.AddToDictionary(">", "&gt;");
                dictXmlTags.AddToDictionary("'", "&apos;");
                dictXmlTags.AddToDictionary("\"", "&quot;");

                if (isRestoreOp) dictXmlTags._ForEach(tag => data = data.Replace(tag.Value, tag.Key));
            }

            return data;
        }

        #region Encoding
        public static string Base64Encode(this string plainText)
        {
            return Convert.ToBase64String(Encoding.UTF8.GetBytes(plainText));
        }

        public static string Base64Decode(this string base64EncodedData)
        {
            return Encoding.UTF8.GetString(Convert.FromBase64String(base64EncodedData));
        }
        #endregion Encoding
        #endregion String

        #region File extensions
        /// <summary>
        /// Adds '*.' characters to file extension
        /// </summary>
        /// <param name="value">String to format</param>
        /// <returns></returns>
        public static string AddExtnChars(this string value)
        {
            return "*." + value.RemoveText(".", "*"); // RKD : (".*".ToCharArray().Select(val => val.ToString()).ToArray());
        }

        /// <summary>
        /// Removes '*.' characters from file extension
        /// </summary>
        /// <param name="value">String to format</param>
        /// <returns></returns>
        public static string RemoveExtnChars(this string value)
        {
            return value.RemoveText(".", "*");
        }

        /// <summary>
        /// Masks &apos;,&apos; characters in CSV elements into &apos;%2C&apos;
        /// </summary>
        /// <param name="value">CSV element to format</param>
        /// <returns></returns>
        public static string MaskCSVChars(this string value)
        {
            return value.Replace(Utilities.cCSVDelim.ToString(), Utilities.sCSVMask);
        }

        /// <summary>
        /// Restores &apos;,&apos; characters in CSV elements from mask &apos;%2C&apos;
        /// </summary>
        /// <param name="value">CSV element to format</param>
        /// <returns></returns>
        public static string RestoreCSVChars(this string value)
        {
            return value.Replace(Utilities.sCSVMask, Utilities.cCSVDelim.ToString());
        }

        /// <summary>
        /// Masks invalid XML characters in elements into corresponding escape sequence;
        /// </summary>
        /// <param name="value">XML element to format</param>
        /// <returns></returns>
        public static string MaskXMLChars(this string value)
        {
            Utilities.dictXMLMask._ForEach(pair => value.Replace(pair.Key, pair.Value));
            return value;
        }

        /// <summary>
        /// Restores XML characters in elements from corresponding escape sequence
        /// </summary>
        /// <param name="value">XML element to format</param>
        /// <returns></returns>
        public static string RestoreXMLChars(this string value)
        {
            Utilities.dictXMLMask._ForEach(pair => value.Replace(pair.Value, pair.Key));
            return value;
        }
        #endregion File extensions

        #region UI
        public static System.Drawing.Point AddToLocation(this System.Drawing.Point value, int x, int y)
        {
            value.X += x;
            value.Y += y;

            return value;
        }

        public static System.Drawing.Size AddToSize(this System.Drawing.Size value, int height, int width)
        {
            value.Height += height;
            value.Width += width;

            return value;
        }

        public static CheckState GetCheckState(this bool? value)
        {
            switch (value)
            {
                case true: return CheckState.Checked;
                case false: return CheckState.Unchecked;
                default: return CheckState.Indeterminate;
            }
        }

        public static CornerRadius UpdateRadius(this CornerRadius rad, bool isDirect,
            Dictionary<eUIDirection, double> dictDirection = null)
        {
            double multiplier = (isDirect ? 1 : -1);
            double offsetAll = multiplier * dictDirection.GetValueFromKey(eUIDirection.All);

            if (!dictDirection.Keys.HasCount(0))
            {
                if (dictDirection._ContainsKey(eUIDirection.All))
                {
                    double offset = multiplier * offsetAll;
                    rad.BottomLeft -= offset;
                    rad.BottomRight -= offset;
                    rad.TopLeft -= offset;
                    rad.TopRight -= offset;
                }
                else
                {
                    rad.BottomLeft -= multiplier * dictDirection.GetValueFromKey(eUIDirection.BottomLeft);
                    rad.BottomRight -= multiplier * dictDirection.GetValueFromKey(eUIDirection.BottomRight);
                    rad.TopLeft -= multiplier * dictDirection.GetValueFromKey(eUIDirection.TopLeft);
                    rad.TopRight -= multiplier * dictDirection.GetValueFromKey(eUIDirection.TopRight);
                }
            }

            return rad;
        }

        public static Thickness MoveMargin(this Thickness value,
            eButtonDirection direction, double shift = SHIFT_DEFAULT)
        {
            double shiftLeft = 0, shiftTop = 0, shiftRight = 0, shiftBottom = 0;
            switch (direction)
            {
                case eButtonDirection.Top:
                    shiftTop = value.Top + SHIFT_DEFAULT;
                    break;
                case eButtonDirection.Bottom:
                    shiftBottom = value.Bottom + SHIFT_DEFAULT;
                    break;
                case eButtonDirection.Left:
                    shiftLeft = value.Left + SHIFT_DEFAULT;
                    break;
                case eButtonDirection.Right:
                    shiftRight = value.Right + SHIFT_DEFAULT;
                    break;
                case eButtonDirection.Default:
                default:
                    break;
            }

            return value.MoveMargin(shiftLeft, shiftTop, shiftRight, shiftBottom);
        }

        private static Thickness MoveMargin(this Thickness value, double shiftLeft, double shiftTop, double shiftRight, double shiftBottom)
        {
            return new Thickness(value.Left + shiftLeft, value.Top + shiftTop,
                value.Right + shiftRight, value.Bottom + shiftBottom);
        }

        /// <summary>
        /// Generates a collection of DataGridViewColumns against a specified entity
        /// </summary>
        /// <typeparam name="TEntity">Entity to be referenced to generate columns</typeparam>
        /// <param name="dgValue">DataGridView to be updated</param>
        /// <param name="dgOutput">Same DataGridView as <paramref name="dgValue"/></param>
        /// <param name="displayUniqueAsLinks">TRUE: Displays unique fields as Hyperlinks</param>
        /// <param name="generateNonBrowsable">TRUE: Specifies if properties marked with BrowsableAttribute(false) should be generated and made hidden
        /// <para>FALSE: Skips generating the columns</para></param>
        /// <returns>Collection of DataGridViewColumns</returns>
        public static void GenerateDGVColumns<TEntity>(this DataGridView dgValue, bool displayUniqueAsLinks)
            where TEntity : class, new()
        {
            System.Windows.Forms.MessageBox.Show("RKD");

            TEntity ent = new TEntity();
            frmDataGridView frmDataGridView = new frmDataGridView();
            ObservableCollection<DataGridViewColumn> output = new ObservableCollection<DataGridViewColumn>();

            DataGridViewCell cellTemplate = frmDataGridView.dcTextBox.CellTemplate;
            DataGridViewLinkCell linkTemplate = (DataGridViewLinkCell)frmDataGridView.dcLink.CellTemplate;
            DataGridViewCheckBoxCell chkTemplate = (DataGridViewCheckBoxCell)frmDataGridView.dcCheckBox.CellTemplate;

            dgValue.AutoGenerateColumns = false;
            dgValue.Columns.Clear();

            foreach (PropertyInfo prop in (typeof(TEntity)).GetProperties())
            {
                string colDataProperty = prop.Name;
                string colId = "col" + colDataProperty;
                string colHeader = ent.GetPropertyAttributeProperty<TEntity, string>(prop.Name, "DisplayNameAttribute", "DisplayName", colDataProperty);

                bool isBrowsable = ent.GetPropertyAttributeProperty<TEntity, bool>(prop.Name, "BrowsableAttribute", "Browsable", BrowsableAttribute.Default.Browsable);
                bool isUnique = ent.GetPropertyAttributeProperty<TEntity, bool>(prop.Name, "UniqueAttribute", "IsUnique", false);

                if (isBrowsable)
                {
                    if (isUnique & displayUniqueAsLinks)
                    {
                        DataGridViewLinkColumn dcValue = new DataGridViewLinkColumn();
                        dcValue.Visible = isBrowsable;

                        // Design
                        dcValue.CellTemplate = linkTemplate;
                        dcValue.UseColumnTextForLinkValue = false;
                        dcValue.LinkBehavior = LinkBehavior.HoverUnderline;
                        dcValue.LinkColor = Color.Blue;
                        dcValue.ActiveLinkColor = Color.Green;
                        dcValue.TrackVisitedState = true;
                        dcValue.VisitedLinkColor = Color.Purple;

                        // Data
                        dcValue.Name = colId;
                        dcValue.DataPropertyName = colDataProperty;
                        dcValue.HeaderText = colHeader;
                        dcValue.ValueType = prop.PropertyType;

                        dgValue.Columns.Add(dcValue);
                    }
                    else if (prop.PropertyType == typeof(bool))
                    {
                        DataGridViewCheckBoxColumn dcValue = new DataGridViewCheckBoxColumn();

                        // Design
                        dcValue.CellTemplate = chkTemplate;

                        // Data
                        dcValue.Name = colId;
                        dcValue.DataPropertyName = colDataProperty;
                        dcValue.HeaderText = colHeader;
                        dcValue.ValueType = prop.PropertyType;

                        dgValue.Columns.Add(dcValue);
                    }
                    else
                    {
                        DataGridViewColumn dcValue = new DataGridViewColumn();
                        dcValue.Visible = isBrowsable;

                        // Design
                        dcValue.CellTemplate = cellTemplate;

                        // Data
                        dcValue.Name = colId;
                        dcValue.DataPropertyName = colDataProperty;
                        dcValue.HeaderText = colHeader;
                        dcValue.ValueType = prop.PropertyType;

                        dgValue.Columns.Add(dcValue);
                    }
                }
            }
        }

        public static void PopulateComboBoxWithEnum<TEnum>(this ComboBox cmb, EnumFormatterDelegate formatter = null, TEnum? selection = null, bool? isSortAscending = null)
            where TEnum : struct
        {
            if (!new TEnum().IsType<Enum>(eMatchType.Hierarchy))
                throw new InvalidOperationException(Resources.Extn_Enum_NotEnumError.FormatTextFromParams(typeof(TEnum)));

            ObservableCollection<IdValueMember<TEnum, string>> dict = new ObservableCollection<IdValueMember<TEnum, string>>();
            new TEnum()._ForEachEnum(val => dict.Add(new IdValueMember<TEnum, string>(val, Convert.ToString(formatter.IsNull() ? val : formatter(val)))), isSortAscending);

            cmb.ValueMember = "Id";
            cmb.DisplayMember = "Value";
            cmb.DataSource = dict.ToObservableCollection();
            cmb.SelectedValue = (selection.IsNull() ? new TEnum() : selection.Value);
        }

        public static void PopulateSortableProperties<TSource>(this ListControl cmbSort, DataGridView dgvData)
           where TSource : class, new()
        {
            TSource ts = new TSource();

            ObservableCollection<IdValueMember<string, string>> sortOptions = new ObservableCollection<IdValueMember<string, string>>();
            ts.GetType().GetProperties().ForEach(prop =>
            {
                bool isSortable = AttributeLists<TSource>.PropertyFieldsSortable._Contains(prop.Name); // ts.GetPropertyAttributeProperty<TSource, bool>(prop.Name, "SortableAttribute", "IsSortable", false);
                string displayText = AttributeLists<TSource>.PropertyMapDisplayName[prop.Name]; // ts.GetPropertyAttributeProperty<TSource, string>(prop.Name, "DisplayNameAttribute", "DisplayName", prop.Name);
                if (displayText.IsNullOrEmpty()) throw new InvalidOperationException("Missing 'DisplayName' for 'DisplayNameAttribute' of property '" + prop.Name + "'");

                if (isSortable && dgvData.Columns["col" + prop.Name] != null)
                    sortOptions.Add(new IdValueMember<string, string>(prop.Name, displayText));
            });

            cmbSort.ValueMember = "Id";
            cmbSort.DisplayMember = "Value";
            cmbSort.DataSource = sortOptions.ToObservableCollection();
            cmbSort.SelectedValue = "ByteSize";
        }
        #endregion UI

        #region Memory
        public static string ToString(this double value, eByteValueType valueType)
        {
            // RKD: develop generic code
            // MemMgmt: Add <abbreviation logic> here for new byte-type
            switch (valueType)
            {
                case eByteValueType.B:
                    return value + "B";
                case eByteValueType.KB:
                    return value + "K";
                case eByteValueType.MB:
                    return value + "M";
                case eByteValueType.GB:
                    return value + "G";
                case eByteValueType.TB:
                    return value + "T";
                default:
                    throw new InvalidOperationException("Improper value type");
            }
        }

        public static string ToPracticalSize(this long? byteSize)
        {
            return byteSize.IsNull() ? DEFAULT_SIZE : byteSize.Value.ToPracticalSize();
        }

        public static string ToPracticalSize(this long byteSize)
        {
            return new MemoryMgmt(eByteValueType.B, byteSize).PracticalSize;
        }
        #endregion Memory

        #region Property
        public static object GetValue(this PropertyInfo prop, object obj)
        {
            return prop.IsNull() ? null : prop.GetValue(obj, null);
        }
        #endregion Property

        #region Validations
        /// <summary>
        /// Returns a Boolean value indicating whether an expression has no object assigned to it.
        /// </summary>
        /// <param name="value">Expression to evaluate</param>
        /// <returns>TRUE: if expression is null; else FALSE</returns>
        public static bool IsNull(this object value)
        {
            return Information.IsNothing(value);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string Coalesce(this string value)
        {
            return value.IsBlank() ? String.Empty : value;
        }

        /// <summary>
        /// RKD
        /// Masks a null value to the specified default value
        /// <para>(Note: If a string is evaluated, it should return blank when the specified default value is null)</para>
        /// </summary>
        /// <typeparam name="TOut"></typeparam>
        /// <param name="value"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public static TOut Coalesce<TOut>(this object value, TOut defaultValue = default(TOut))
        {
            try
            {
                // If a string is evaluated, it should return blank when default value is null
                if (typeof(TOut) == typeof(string) && defaultValue.IsNull())
                    return Convert.ToString(value).Coalesce().ChangeType<TOut>();
                else return value.IsNull() ? defaultValue : value.ChangeType<TOut>();
            }
            catch (Exception)
            {
                return defaultValue;
            }
        }

        public delegate TOut Delgt<TOut>();
        public static TOut TryGet<TOut>(Delgt<TOut> value, TOut defaultValue = default(TOut))
        {
            try
            {
                return value();
            }
            catch (Exception)
            {
                return defaultValue;
            }
        }

        public static TOut TryGet2<TOut>(this TOut value, TOut defaultValue = default(TOut))
        {
            try
            {
                return value;
            }
            catch (Exception)
            {
                return defaultValue;
            }
        }

        /// <summary>
        /// RKD
        /// </summary>
        /// <typeparam name="TOut">Type of the value of the property</typeparam>
        /// <param name="value">Object to extract property value from</param>
        /// <param name="propertyName">Name of the property to be extracted</param>
        /// <param name="defaultValue">Default value of the property if <paramref name="value"/> evaluates to null</param>
        /// <returns></returns>
        public static TOut Coalesce<TSource, TOut>(this TSource value, string propertyName, TOut defaultValue = default(TOut))
        {
            try
            {
                return value.IsNull() ? defaultValue : value.GetType().GetProperty(propertyName).GetValue(value, null).ChangeType<TOut>().Coalesce<TOut>(defaultValue);
            }
            catch (Exception)
            {
                return defaultValue;
            }
        }

        /// <summary>
        /// Validates if specified value is numeric
        /// </summary>
        /// <param name="value">Value to validate</param>
        /// <returns>TRUE: if specified value is numeric</returns>
        public static bool IsNumeric(this object value)
        {
            return Information.IsNumeric(value);
        }

        /// <summary>
        /// Validates if specified value contains only alphabets
        /// </summary>
        /// <param name="value">Text to validate</param>
        /// <param name="length">Length to be validated</param>
        /// <returns>TRUE: if specified value contains only alphabets</returns>
        public static bool IsAlpha(this string value, int length = 0)
        {
            if (length < 0) throw new ArgumentException("Length cannot be negative!");
            if (length > 0 & value.Length > length) return false;

            bool result = true;
            if (value.IsNumeric()) result = false;
            else
            {
                value.ToLower().ToCharArray()._ForEach(chr =>
                    {
                        if (!Utilities.ValidateBetween((int)chr, (int)'a', (int)'z', true))
                            result = false;
                    });
            }

            return result;
        }

        /// <summary>
        /// Checks if specified &apos;<paramref name="value"/>&apos;
        /// is default value of type specified by &apos;<paramref name="TSource"/>&apos;
        /// </summary>
        /// <typeparam name="TSource">Type of data</typeparam>
        /// <param name="value">Data to be validated</param>
        /// <returns>TRUE: if specified value is of default value of the specified type</returns>
        public static bool IsDefault<TSource>(this TSource value)
        {
            if (value.IsNull()) value = default(TSource);

            if (default(TSource).IsNull()) return value.IsNull();
            else // might throw SerializationException
                return value.SerializeToFormattedData<TSource>(eSerializationFormat.JSon)
                    == default(TSource).SerializeToFormattedData<TSource>(eSerializationFormat.JSon);
        }
        #endregion Validations

        #region Conversions : Type
        /// <summary>
        /// Checks if specified &apos;<paramref name="value"/>&apos;
        /// is of type specified by &apos;<paramref name="TSource"/>&apos;
        /// </summary>
        /// <typeparam name="TSource">Type of data</typeparam>
        /// <param name="value">Data to be validated</param>
        /// <param name="matchType">Class inheritence matching type</param>
        /// <returns></returns>
        public static bool IsType<TSource>(this object value, eMatchType matchType)
        {
            // (typeof(TEnum).BaseType != typeof(Enum))

            if (matchType == eMatchType.Exact)
                return typeof(TSource) == value.GetType();
            else if (matchType == eMatchType.Hierarchy)
                return CanConvert<TSource>(value);

            return false;
        }

        /// <summary>
        /// Checks if specified value can be converted to the type specified by &apos;<paramref name="TSource"/>&apos;
        /// </summary>
        /// <typeparam name="TSource">Type of data</typeparam>
        /// <param name="value">Data to be validated</param>
        /// <returns>TRUE: if data can be converted to the specified type</returns>
        public static bool CanConvert<TSource>(this object value)
        {
            try
            {
                TSource res = (TSource)value;
                return true;
            }
            catch (Exception) { return false; }
        }

        /// <summary>
        /// Returns an object of the specified type &apos;<paramref name="TOut"/>&apos;
        /// and whose value is equivalent to the specified object &apos;<paramref name="value"/>&apos;
        /// </summary>
        /// <typeparam name="TOut">Type of the object to return</typeparam>
        /// <param name="value">Value to convert</param>
        /// <returns></returns>
        public static TOut ChangeType<TOut>(this object value)
        {
            return (TOut)Convert.ChangeType((typeof(TOut) == typeof(string)) ? value.ToString() : value, typeof(TOut));
        }
        #endregion Conversions : Type

        #region TimeSpan
        /// <summary>
        /// Formats time span
        /// <para>(Default format if skipped: &quot;ss.fff&quot;)</para>
        /// </summary>
        /// <param name="value">TimeSpan to be formatted</param>
        /// <param name="format">Format: [-][d.]hh:mm:ss[.fffffff]
        /// <para>"-"         : A minus sign, which indicates a negative time interval. No sign is included for a positive time span.</para>
        /// <para>"d"         : The number of days in the time interval. This element is omitted if the time interval is less than one day.</para>
        /// <para>"hh"        : The number of hours in the time interval, ranging from 0 to 23.</para>
        /// <para>"mm"        : The number of minutes in the time interval, ranging from 0 to 59.</para>
        /// <para>"ss"        : The number of seconds in the time interval, ranging from 0 to 59.</para>
        /// <para>"fffffff"   : Fractional seconds in the time interval. This element is omitted if the time interval does not include fractional seconds.</para>
        ///                     <para>If present, fractional seconds are always expressed using seven decimal digits.</para></param>
        /// <returns></returns>
        public static string FormatTimeSpan(this TimeSpan value, string format = null)
        {
            format = format.Coalesce("ss.fff");
            format.Split(":.".ToCharArray(), StringSplitOptions.None)._ForEach(f =>
            {
                try { format = format.Replace(f, value.ToString(f)); }
                catch (Exception) { }
            });

            return format;
        }
        #endregion TimeSpan

        #region Episodes
        /// <summary>
        /// Adds separator between season/episode index and episode title
        /// </summary>
        /// <param name="nameSugg">Suggested file name</param>
        /// <returns></returns>
        public static string AddSeparator(this string nameSugg)
        {
            if (nameSugg.IsBlank()) return nameSugg;

            string separator = "-";
            List<string> wordList = nameSugg.ToWords().ToList();

            if (wordList.GetCount() < 2) return nameSugg; // Skip for single word
            else if (wordList.ElementAt(1) == separator) return nameSugg; // Skip if already present
            else if (!wordList.CheckEpisodeFileNameIndexPattern()) return nameSugg; // Skip if {s##e##} format not matching
            else
            {
                wordList.Insert(1, "-");
                return wordList.ToSentence();
            }
        }

        /// <summary>
        /// Checks for season/episode index pattern &apos;s##e##&apos;
        /// for the first word of the specified word list
        /// </summary>
        /// <param name="wordList">List of words</param>
        /// <returns></returns>
        private static bool CheckEpisodeFileNameIndexPattern(this IEnumerable<string> wordList)
        {
            return wordList.FirstOrDefault().Coalesce().CheckEpisodeFileNameIndexPattern();
        }

        /// <summary>
        /// Checks for season/episode index pattern &apos;s##e##&apos;
        /// for the first word of the specified text
        /// </summary>
        /// <param name="sIndexPart">Text part to validate</param>
        /// <returns></returns>
        public static bool CheckEpisodeFileNameIndexPattern(this string sIndexPart)
        {
            sIndexPart = sIndexPart.ToWords().FirstOrDefault().Coalesce();
            if (sIndexPart.Length < 6) return false;

            string pattern = @"s[\d]{2}e[\d]{2}";
            return Regex.IsMatch(sIndexPart.Trim().TrimAndTake(6, eTrimDirection.Left, false), pattern);

            //List<char> charList = format.ToLower().ToCharArray().ToList();
            //return charList.First() == 's' && format.Substring(1, 2).IsNumeric()
            //    && charList.Take(4).Last() == 'e' && format.Substring(4, 2).IsNumeric();
        }
        #endregion Episodes

        internal static string GetAttribName(this Attribute attr)
        {
            string sAttribute = "Attribute";
            return attr.ToString().Split('.').Last().Replace(sAttribute, "");
        }

        public static CultureInfo GetCulture(this CultureInfo culture)
        {
            if (culture.IsNull()) culture = Utilities.DefaultCulture;
            return culture;
        }
        // NumToWords RKD
        public static IEnumerable<TSource> ToCollection<TSource>(this TSource value)
            where TSource : new()
        {
            IEnumerable<TSource> result = new ObservableCollection<TSource>();
            result.AddItem(value);

            return result;
        }

        /// <summary>
        /// 'Shallow copies' duplicate as little as possible. A shallow copy of a collection is a copy of the collection structure, not the elements. With a shallow copy, two collections now share the individual elements.
        /// 'Deep copies' duplicate everything. A deep copy of a collection is two collections with all of the elements in the original collection duplicated.
        /// https://en.wikipedia.org/wiki/Object_copying
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="value"></param>
        /// <returns></returns>
        public static TSource ShallowCopy<TSource>(this TSource value)
            where TSource : new()
        {
            return value.SerializeToFormattedData(eSerializationFormat.Xml)
                .DeSerializeFromFormattedData<TSource>(eSerializationFormat.Xml);
        }
        #endregion Extension methods

        #region Methods
        public static string ToString()
        {
            return Utilities.ToString(typeof(MiscExtensions));
        }
        #endregion Methods
    }
}
